﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Hammock;
using TweetSharp;

namespace Microsoft.Coding4Fun.TweetCraft.Twitter
{
    /// <summary>
    /// Retrieves tweets and adds new status updates to the user's Twitter feed using TweetSharp.
    /// </summary>
    public class TwitterClient : ITwitterClient
    {
        private const int MinTakeCount = 5;
        private const int MaxTakeCount = 100;

        private readonly int takeCount;
        private TwitterClientStatus clientStatus;
        private static readonly string consumerKey;
        private static readonly string consumerSecret;
        private static readonly string twitPicKey;

        public string UserName { get; set; }
        public string Token { get; set; }
        public SecureString TokenSecret { get; set; }

        /// <summary>
        /// Gets the status of the Twitter client.
        /// </summary>
        /// <value></value>
        public TwitterClientStatus Status
        {
            get { return clientStatus; }
            private set
            {
                if (clientStatus != value)
                {
                    clientStatus = value;
                    OnStatusChanged();
                }
            }
        }

        /// <summary>
        /// Raised when the <see cref="Status"/> changes.
        /// </summary>
        public event EventHandler StatusChanged;

        private void OnStatusChanged()
        {
            if (StatusChanged != null)
            {
                StatusChanged(this, EventArgs.Empty);
            }
        }
        
        static TwitterClient()
        {
            // Set OAuth consumer to identify this client
            consumerKey = Settings.Default.ConsumerKey;
            consumerSecret = Settings.Default.ConsumerSecret;
            twitPicKey = Settings.Default.TwitPicKey;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TwitterClient"/> class.
        /// </summary>
        public TwitterClient()
        {
            // Make sure the RetrieveTweetCount setting is within bounds
            takeCount = Math.Min(Math.Max(MinTakeCount, Settings.Default.RetrieveTweetCount), MaxTakeCount);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TwitterClient"/> class.
        /// </summary>
        /// <param name="token">The Twitter OAuth token.</param>
        /// <param name="tokenSecret">The Twitter OAuth token secret.</param>
        public TwitterClient(string token, SecureString tokenSecret) : this()
        {
            if (token == null)
            {
                throw new ArgumentNullException("token");
            }
            if (tokenSecret == null)
            {
                throw new ArgumentNullException("tokenSecret");
            }

            Token = token;
            TokenSecret = tokenSecret;
        }

        public string[] GetRequestToken()
        {
            using (TraceScope.Information("Retrieving OAuth request token..."))
            {
                var service = new TweetSharp.TwitterService(Settings.Default.ConsumerKey, Settings.Default.ConsumerSecret);
                
                var token = service.GetRequestToken();
                
                return new[] { token.Token, token.TokenSecret };    
            }
        }

        public string[] GetAccessToken(string[] requestToken, string verifier)
        {
            using (TraceScope.Information("Retrieving OAuth access token..."))
            {
                var service = new TweetSharp.TwitterService(Settings.Default.ConsumerKey, Settings.Default.ConsumerSecret);
                
                var token = service.GetAccessToken(new OAuthRequestToken {Token = requestToken[0], TokenSecret = requestToken[1]}, verifier);
                
                return new[] { token.Token, token.TokenSecret };
            }
        }

        public Tweet[] GetPublicTimeline()
        {
            using (TraceScope.Information("Retrieving public timeline..."))
            {
                var service = new TweetSharp.TwitterService(consumerKey, consumerSecret);

                var statuses = service.ListTweetsOnPublicTimeline();

                return GetResponseAsTweets(service, statuses);
            }
        }

        public Tweet[] GetFriendsTimeline()
        {
            using (TraceScope.Information("Retrieving friends' tweets..."))
            {
                var service = GetAuthenticatedService();

                var statuses = service.ListTweetsOnFriendsTimeline(takeCount);

                return GetResponseAsTweets(service, statuses);
            }
        }

        public Tweet[] GetMentions()
        {
            using (TraceScope.Information("Retrieving mentions..."))
            {
                var service = GetAuthenticatedService();

                var statuses = service.ListTweetsMentioningMe(takeCount);

                return GetResponseAsTweets(service, statuses);
            }
        }

        public Tweet AddTweet(string message)
        {
            if (message == null)
            {
                throw new ArgumentNullException("message");
            }

            using (TraceScope.Information("Adding tweet..."))
            {
                var service = GetAuthenticatedService();

                var status = service.SendTweet(message);

                return GetResponseAsTweet(service, status);
            }
        }

        /// <summary>
        /// Uploads and posts a message and a picture to Twitter.
        ///</summary>
        ///<param name="message">The message.</param>
        ///<param name="picturePath">The path to the picture.</param>
        public Tweet UploadAndPost(string picturePath, string message)
        {
            if (picturePath == null)
            {
                throw new ArgumentNullException("picturePath");
            }
            if (message == null)
            {
                throw new ArgumentNullException("message");
            }

            // http://dev.twitpic.com/docs/2/upload/
            using (TraceScope.Information("Posting picture using TwitPic..."))
            {
                // Prepate an OAuth echo request to TwitPic
                var service = GetAuthenticatedService();
                var request = service.PrepareEchoRequest(); // <-- OAuth delegation
                request.Path = "upload.xml";
                request.AddFile("media", Path.GetFileNameWithoutExtension(picturePath), picturePath, "image/jpeg");
                request.AddField("key", twitPicKey);
                //request.AddField("message", message);

                // Post photo to TwitPic
                var client = new RestClient { Authority = "http://api.twitpic.com/", VersionPath = "2"};
                var response = client.Request(request);
                
                #region TwitPic Response XML
                //<image>
                //    <id>37z52z</id>
                //    <text />
                //    <url>http://twitpic.com/slug</url>
                //    <width>48</width>
                //    <height>48</height>
                //    <size>2724</size>
                //    <type>jpg</type>
                //    <timestamp>Thu, 18 Nov 2010 19:28:09 +0000</timestamp>
                //    <user>
                //        <id>123456</id>
                //        <screen_name>user</screen_name>
                //    </user>
                //</image>
                #endregion

                // Get posted image URL
                var match = Regex.Match(response.Content, "<url[^>]*>(.*?)</url>");
                var mediaUrl = XElement.Parse(match.Value).Value;
                
                // Send the tweet with the media URL
                var status = service.SendTweet(string.Format("{0} - {1}", message, mediaUrl));
                return GetResponseAsTweet(service, status);
            }
        }

        public Tweet UploadAndPost(string picturePath)
        {
            return UploadAndPost(picturePath, String.Empty);
        }

        public static bool ValidateAccount(string token, SecureString tokenSecret)
        {
            if (token == null)
            {
                throw new ArgumentNullException("token");
            }
            if (tokenSecret == null)
            {
                throw new ArgumentNullException("tokenSecret");
            }

            using (TraceScope.Information("Validating twitter account..."))
            {
                var service = new TweetSharp.TwitterService(consumerKey, consumerSecret);
                service.AuthenticateWith(token, tokenSecret.Unwrap());
                
                var response = service.VerifyCredentials();
                
                 // Return true, if we have a valid User response
                return (service.Response.StatusCode == HttpStatusCode.OK && response != null && response.Id != 0);
            }
        }
        
        private Tweet[] GetResponseAsTweets(TweetSharp.TwitterService service, IEnumerable<TwitterStatus> statuses)
        {
            if (statuses == null)
            {
                throw new TwitterException(
                    Strings.UnknownErrorMessage, service.Response != null ? service.Response.Response : ""
                    );
            }
            
            if(statuses.Count() == 1)
            {
                var error = service.Deserialize<TwitterError>(statuses.First());
                if(!string.IsNullOrEmpty(error.ErrorMessage))
                {
                    Status = GetErrorStatus(error.ErrorMessage);
                    statuses = new List<TwitterStatus>(0);
                }
            }

            // Perform the actual request and return tweets
            return statuses.ToTweets().ToArray();
        }

        private Tweet GetResponseAsTweet(TweetSharp.TwitterService service, TwitterStatus status)
        {
            if(service.Response != null && service.Response.StatusCode != HttpStatusCode.OK)
            {
                var model = (ITwitterModel) status;
                var error = service.Deserialize<TwitterError>(model);
                Status = GetErrorStatus(error.ErrorMessage);

                if(!string.IsNullOrEmpty(error.ErrorMessage))
                {
                    // This was an actual Twitter Error
                    throw new TwitterException(Strings.TwitterFailed, service.Response.Response);
                }
                throw new TwitterException(Strings.UnknownErrorMessage, service.Response.Response);
            }

            if (status == null || status.Id.Equals(0)) 
            {
                throw new TwitterException(
                    Strings.UnknownErrorMessage, service.Response != null ? service.Response.Response : ""
                    );
            }

            // Perform the actual request and return tweets
            return status.ToTweet();
        }

        private TweetSharp.TwitterService GetAuthenticatedService()
        {
            var service = new TweetSharp.TwitterService(consumerKey, consumerSecret);
            service.AuthenticateWith(Token, TokenSecret.Unwrap());
            return service;
        }

        private static TwitterClientStatus GetErrorStatus(string errorMessage)
        {
            if (errorMessage != null)
            {
                if (errorMessage.Contains("Rate limit exceeded"))
                {
                    return TwitterClientStatus.NotConnectedRateLimitExceeded;
                }
            }

            return TwitterClientStatus.NotConnected;
        }
    }
}
